<?php


namespace App\Services;

use App\Events\UserReadChapterContent;
use App\Exceptions\ApiException;
use App\Format\LastChapterFormat;
use App\Format\LastReadFormat;
use App\Format\NovelDetailFormat;
use App\Models\Banner;
use App\Models\Chapter;
use App\Models\ConsumptionLog;
use App\Models\Essence;
use App\Models\Novel;
use App\Models\Tag;
use App\Models\User;
use App\Models\UserReadLog;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;
use \Exception;

class NovelService
{
    const BUY_COUPON_LOCK_PRE = 'buy_coupon_lock_%s';

    const DEFAULT_COUPON = 20;

    public function __construct()
    {
    }

    public function getEssList()
    {
        $list = Essence::query()->where('hide', 'N')
            ->orderBy('order', 'desc')->get()->toArray();

        if (empty($list)) {
            return [];
        }

        $this->fillEssenceList($list);

        return toCamelCaseKey($list);
    }

    public function getBanner()
    {
        $res = Banner::query()->select('id as banner_id', 'url', 'novel_id', 'order')
            ->get()->toArray();

        return toCamelCaseKey($res);
    }

    public function initEssence()
    {
        $novelids = Novel::query()->inRandomOrder()->limit(4)->get()->pluck('id')->toArray();
        Essence::query()->create([
            'name' => '智慧冲冲冲',
            'novel_ids' => implode(',', $novelids),
            'template_type' => '4N',
            'max_book_num' => 4,
            'order' => 0,
        ]);
    }

    public function setEssence($novelIds, $name)
    {
        Essence::query()->create([
            'name' => $name,
            'novel_ids' => implode(',', $novelIds),
            'template_type' => '4N',
            'max_book_num' => 4,
            'order' => 0,
        ]);
    }

    public function initBanner()
    {
        Banner::query()->create([
            'url' => 'http://image.hanlingsha.love/lwa/pic/2020-10-16/8CB34B7B6F0FF3521CB866BEF1B35AC0.jpg',
            'novel_id' => 1,
            'order' => 0,
        ]);
    }

    public function getNovelDetail($novelId, $userId)
    {
        $detail = Novel::query()->where('id', $novelId)
            ->first();
        if (empty($detail)) {
            throw new ApiException('书籍不存在', 1);
        }
        if ($detail->is_hide == 'Y') {
            throw new ApiException('书籍被下架', 1);
        }

        // 书籍信息
        $novelInfoFormat = new NovelDetailFormat($detail->toArray());
        $novelInfoFormat->setNovelId($detail->id);
        $novelInfoFormat->setRate(mb_strlen($novelInfoFormat->getIntro()) % 5);
        $lastOrderNum = $novelInfoFormat->getChapterNum() == 0 ? 1 : $novelInfoFormat->getChapterNum();
        $novelInfoFormat->setUpdateTime($this->findNovelUpdateTime($novelId));

        // 上次更新
        $lastChapterInfo = $this->findLastChapterInfo($novelId, $lastOrderNum);
        $lastChapterFormat = new LastChapterFormat($lastChapterInfo);

        // 最近阅读
        $lastRead = [];
        if ($userId) {
            $lastRead = $this->findLastRead($userId, $novelId);
        }
        $lastReadFormat = new LastReadFormat($lastRead);

        $result = [
            'novel_info' => $novelInfoFormat->toArrayNotNull(),
            'last_chapter' => $lastChapterFormat->toArrayNotNull(),
            'last_read' => $lastReadFormat->toArrayNotNull(),
        ];

        return toCamelCaseKey($result);
    }

    public function getChapterList($novelId, $userId)
    {
        $chapters = Chapter::query()->where('novel_id', $novelId)
            ->where('publish', 'Y')
            ->orderBy('order_num')
            ->select('id', 'is_vip', 'chapter_name', 'order_num')
            ->get()
            ->toArray();

        $userBought = [];
        $userRead = [];
        if ($userId) {
            $userBought = $this->getUserBought($userId, $novelId);
            $userRead = $this->getUserReadLog($userId, $novelId);
        }

        $userBoughtMap = array_column($userBought, null, 'chapter_id');
        $userReadMap = array_column($userRead, null, 'chapter_id');


        array_walk($chapters, function (&$row) use ($userBoughtMap, $userReadMap) {
            $row['bought'] = isset($userBoughtMap[$row['id']]) ? 'Y' : 'N';
            $row['readed'] = isset($userReadMap[$row['id']]) ? 'Y' : 'N';
        });

        return toCamelCaseKey($chapters);
    }

    public function getUserBought($userId, $novelId)
    {
        return ConsumptionLog::query()->where('user_id', $userId)
            ->where('novel_id', $novelId)
            ->select('novel_id', 'chapter_id', 'order_num', 'user_id')
            ->get()->toArray();
    }

    public function getUserReadLog($userId, $novelId)
    {
        return UserReadLog::query()->where('user_id', $userId)
            ->where('novel_id', $novelId)
            ->select('user_id', 'novel_id', 'chapter_id')
            ->get()
            ->toArray();
    }

    public function searchNovel($searchKey)
    {
        $search = Novel::query()->where('is_hide', 'N')
            ->where('novel_name', 'like', '%' . $searchKey . '%')
            ->select('id as novel_id', 'novel_name', 'cover_url', 'tag_name', 'author_name')
            ->get()
            ->toArray();

        return toCamelCaseKey($search);
    }

    public function getChanpterContent($novelId, $orderNum, $userId, $isPay)
    {
        $lockKey = sprintf(self::BUY_COUPON_LOCK_PRE, $userId);
        // Check Redis Lock
        if (Redis::get($lockKey) == 'Y') {
            throw new ApiException('操作频繁', 1);
        }

        /* @var $chapter Chapter */
        $chapter = Chapter::query()->where('novel_id', $novelId)
            ->where('order_num', $orderNum)
            ->where('publish', 'Y')
            ->select('novel_id', 'order_num', 'content', 'is_vip', 'id as chapter_id', 'coupon', 'word_num', 'chapter_name', 'id')
            ->first();

        if (empty($chapter->toArray())) {
            throw new ApiException('章节不存在', 1);
        }

        $novel = Novel::find($novelId);
        $novelName = $novel->novel_name;
        $userCoupon = 0;
        /* @var $user User */
        $user = User::find($userId);
        if ($userId) {
            $userCoupon = $user->coupon;
        }
        $isVip = $chapter['is_vip'];

        $result = [
            'content'       => '',
            'userCoupon'    => $userCoupon,
            'chapterCoupon' => 0,
            'isBought'      => 'N',
            'isVip'         => $isVip,
            'chapterName'   => $chapter['chapter_name'],
            'novelName'     => $novelName,
            'isRead'        => 'Y', // 默认已阅读 为以后可能存在的场景预留 如批量购买但是暂时没有阅读
        ];

        if ($isVip == 'N') {
            $result['content'] = $chapter['content'];
        } else {
            if (! $userId) {
                throw new ApiException('付费章节未登录', 901);
            }
            // 购买过
            if ($this->checkBought($userId, $novelId, $orderNum)) {
                $result['content'] = $chapter->content;
                $result['isBought'] = 'Y';
                goto end;
            }

            $realCoupon = $this->getRealCoupon($novel->coupon, $chapter->coupon, $chapter->word_num);
            $result['chapterCoupon'] = $realCoupon;
            if ($isPay == 'N') {
                $result['content'] = mb_substr($chapter->content, 0, 50) . '...';
                $result['isRead'] = 'N';
                goto end;
            }
            if ($user->coupon < $realCoupon) {
                throw new ApiException('账户书券不足', 1);
            }
            Redis::set($lockKey, 'Y');
            try {
                $this->userBuyChapter($userId, $realCoupon, $novelId, $chapter->chapter_id, $orderNum);
            } catch (Exception $exception) {
                Redis::del($lockKey);
                throw new ApiException($exception->getMessage(), 1);
            }
            Redis::del($lockKey);
            $result['content'] = $chapter->content;
            $result['isBought'] = 'Y';
        }

        end:
        if ($userId && $result['isRead'] == 'Y') {
            event(new UserReadChapterContent($user, $chapter));
        }

        return $result;
    }

    public function getRandomRec()
    {
        $result = Novel::query()->inRandomOrder()
            ->select('id as novel_id', 'novel_name', 'cover_url')
            ->where('is_hide', 'N')
            ->limit(5)
            ->get()->toArray();

        return toCamelCaseKey($result);
    }

    public function createNovel(NovelDetailFormat $novelDetailFormat)
    {
        $novelInfo = $novelDetailFormat->toArrayNotNull();
        $novel = Novel::query()->create($novelInfo);
        return $novel;
    }

    public function getAllNovelTags()
    {
        return toCamelCaseKey(Tag::all()->toArray());
    }

    public function getNovelsByTag($tagId)
    {
        $novels = Novel::query()->where('tag_id', $tagId)
            ->select('id as novel_id', 'novel_name', 'cover_url', 'tag_name', 'words_num', 'author_name')
            ->get()->toArray();

        return toCamelCaseKey($novels);
    }

    private function userBuyChapter($userId, $decrCoupon, $novelId, $chapterId, $orderNum)
    {
        // 减用户书券 增加购买记录
        try {
            DB::beginTransaction();
            $user = User::query()->find($userId);
            $user->coupon = $user->coupon - $decrCoupon;
            $user->save();
            ConsumptionLog::query()->create([
                'user_id' => $userId,
                'novel_id' => $novelId,
                'chapter_id' => $chapterId,
                'coupon' => $decrCoupon,
                'order_num' => $orderNum,
            ]);
            DB::commit();
        } catch (\Exception $exception) {
            DB::rollBack();
            throw new Exception($exception->getMessage(), $exception->getCode());
        }
    }

    private function getRealCoupon($novelCoupon, $chapterCoupon, $wordNum)
    {
        // 最优先取章节本身定价
        if ($chapterCoupon > 0) {
            return $chapterCoupon;
        }

        // 其次取书籍本身的千字定价
        if ($novelCoupon > 0) {
            return ceil(($wordNum / 1000) * $novelCoupon);
        }

        // 都没有设置 取默认的千字定价
        return ceil(($wordNum / 1000) * self::DEFAULT_COUPON);
    }

    private function checkBought($userId, $novelId, $orderNum)
    {
        return ConsumptionLog::query()->where('user_id', $userId)
            ->where('novel_id', $novelId)
            ->where('order_num', $orderNum)
            ->exists();
    }

    private function fillEssenceList(&$list)
    {
        array_walk($list, function (&$row) {
            $novelIds = explode(',', $row['novel_ids']);
            $row['novels'] = $this->findNovelByIds($novelIds);
        });
    }

    private function findNovelByIds($novelIds)
    {
        $novels = Novel::query()->whereIn('id', $novelIds)
            ->select('*', 'id as novel_id')
            ->where('is_hide', 'N')
            ->get()->toArray();

        return $novels;
    }

    private function findNovelUpdateTime($novelId)
    {
        return strtotime(Chapter::query()->where('novel_id', $novelId)
            ->select('created_at')
            ->orderBy('order_num', 'desc')
            ->first()->created_at);
    }

    private function findLastChapterInfo($novelId, $orderNum)
    {
        return Chapter::query()->where('novel_id', $novelId)
            ->where('order_num', $orderNum)
            ->select('chapter_name', 'id as chapter_id', 'novel_id', 'order_num')
            ->first()->toArray();
    }

    private function findLastRead($userId, $novelId)
    {
        return optional(UserReadLog::query()->where('user_id', $userId)
            ->where('novel_id', $novelId)
            ->select('novel_id', 'chapter_id')
            ->limit(1)
            ->orderBy('read_time', 'desc')
            ->first())->toArray() ?? [];
    }
}
